﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System.Collections.Generic;
using Daidalos.Graphics;
using Daidalos.Shaders;

namespace Daidalos.Utilities
{
	public sealed class ResourceManager : Patterns.SingletonManager<ResourceManager>
	{

		#region Dictionaries
		/// <summary>
		///		This dictionary stores references to textures
		///		that have been loaded in the past.
		/// </summary>
		private Dictionary<string, Texture> textureHashes = new Dictionary<string, Texture>();

		/// <summary>
		///		This dictionary stores references to shader sets
		///		that have been loaded in the past.
		/// </summary>
		private Dictionary<string, ShaderSet> shaderHashes = new Dictionary<string, ShaderSet>();
		#endregion

		#region Attributes
		/// <summary>
		///		Stores the search paths for texture files.
		/// </summary>
		private LinkedList<string> textureSearchPaths = new LinkedList<string>();
		#endregion

		#region Constructor
		public ResourceManager()
		{
		}

		~ResourceManager()
		{
		}
		#endregion

		#region Methods
		/// <summary>
		///		This method reloads all textures hashed in the manager.
		/// </summary>
		/// <remarks>
		///		Invoke this method after a device loss.
		/// </remarks>
		public void ReloadTextures()
		{
			// Log the venture.
			LogManager.Instance.Log("Reloading textures.");

			// Store the texture hashmap.
			Dictionary<string, Texture> oldHashes = textureHashes;
			
			// Create a new hashmap.
			textureHashes = new Dictionary<string, Texture>();

			// Reload all textures from the old hashmap.
			foreach (KeyValuePair<string, Texture> P in oldHashes)
			{
				// Validate value.
				if (P.Value == null)
				{
					continue;
				}

				// Dipose of old data.
				P.Value.Dispose();

				// Reload the texture.
				try
				{
					// Reload & Re-register the texture file.
					GetTextureByName(P.Key);
				}
				catch (Exception)
				{
					throw new Exception("Failed to reload textures!");
				}
			}		  
		}

		/// <summary>
		///		This method registers a directory as a texture search path.
		/// </summary>
		/// <param name="path">
		///		The path to add to register.
		/// </param>
		/// <remarks>
		///		Supply paths as relative or absolute paths without tailing
		///		slashes.
		/// </remarks>
		public void AddTextureSearchPath(string path)
		{
			if (path != null && path != "" && !textureSearchPaths.Contains(path))
			{
				// Remove tailing slashes.
				if (path[path.Length - 1] == '/' || path[path.Length - 1] == '\\')
				{
					path = path.Substring(0, path.Length - 1);
				}
				textureSearchPaths.AddLast(path);
			}
		}

		/// <summary>
		///		This method removes a directory from the list of texture
		///		search paths.
		/// </summary>
		/// <param name="path">
		///		The path to remove from the register.
		/// </param>
		public void RemoveTextureSearchPath(string path)
		{
			if (path != null && path != "" && textureSearchPaths.Contains(path))
			{
				textureSearchPaths.Remove(path);
			}
		}

		/// <summary>
		///		This method retrieves a texture by its supplied
		///		name.
		/// </summary>
		/// <param name="name">
		///		The name of the texture.
		/// </param>
		/// <returns>
		///		A texture object.
		/// </returns>
		public Texture GetTextureByName(string name)
		{
			// Lookup the hashmap.
			if (textureHashes.ContainsKey(name))
			{
				// Just return the stored texture.
				return textureHashes[name];
			}
			else
			{
				// Log the venture.
				LogManager.Instance.Log("  + Loading texture \"" + name + "\".");

				// Fetch the texture from file.
				Microsoft.DirectX.Direct3D.Texture handle = FetchTexture(name);

				// Validate the texture.
				if (handle == null)
				{
					throw new Exception("Failed to fetch a texture from a file!");
				}

				// Create an internal texture from the D3D texture.
				Texture texture = new Texture(handle);

				// Register the texture with the hashmap.
				textureHashes.Add(name, texture);

				// Return the texture.
				return texture;
			}		  
		}

		/// <summary>
		///		Returns a shader that is registered with a 
		///		specific name.
		/// </summary>
		/// <param name="name">
		///		The name of the desired shader.
		/// </param>
		/// <returns>
		///		A reference to the shader set.
		/// </returns>
		/// <remarks>
		///		To access shader specific features check
		///		the instance of the returned object and
		///		apply necessary casts.
		/// </remarks>
		public ShaderSet GetShaderByName(string name)
		{
			// Lookup the hashmap.
			if (shaderHashes.ContainsKey(name))
			{
				// Return the shader.
				return shaderHashes[name];
			}
			else
			{
				// Unknown shader type.
				throw new Exception("Unknown shader name!");
			}
		}

		/// <summary>
		///		Register a shader with the resource manager.
		/// </summary>
		/// <param name="name">
		///		The name of the shader.
		/// </param>
		/// <param name="shader">
		///		The shader object.
		/// </param>
		public void RegisterShader(string name, ShaderSet shader)
		{
			// Avoid multiple entries.
			if (!shaderHashes.ContainsKey(name))
			{
				shaderHashes.Add(name, shader);
			}
		}
		#endregion

		#region Private Methods
		/// <summary>
		///		This method loads a texture from a file using the DirectX
		///		texture loader.
		/// </summary>
		/// <param name="name">
		///		The name of the texture file. The internal file name is
		///		linked to the filename to prevent ambiguities.
		/// </param>
		/// <returns>
		///		A handle to the loaded Direct3D texture. Returns null if
		///		the texture was not found.
		/// </returns>
		private Microsoft.DirectX.Direct3D.Texture FetchTexture(string name)
		{
			// Validate the name.
			if (name == null || name == "")
			{
				throw new Exception("Cannot fetch texture due to an invalid name!");
			}

			// Create a handle for the texture.
			Microsoft.DirectX.Direct3D.Texture handle = null;

			// Try to load the texture from the root path.
			try
			{
				handle = Microsoft.DirectX.Direct3D.TextureLoader.FromFile(RenderDevice.Instance.Device, name);
			}
			catch (Microsoft.DirectX.DirectXException)
			{
				// Try to load the texture from any given search path.
				foreach (string path in textureSearchPaths)
				{
					// Construct the new path.
					string texturePath = path + @"\" + name;

					// Try to load the texture from that path.
					try
					{
						handle = Microsoft.DirectX.Direct3D.TextureLoader.FromFile(RenderDevice.Instance.Device, texturePath);
					}
					catch (Microsoft.DirectX.DirectXException)
					{
						// The file was not loaded, try the next path.
						continue;
					}
					break;
				}
			}

			// Return the texture handle.
			return handle;
		}
		#endregion

	}
}
